2

防抖(debounce)和节流(throttle)

连续触发(触发频率很高)的事件,不进行优化,会出现页面卡顿现象。
常见的需要优化的事件:

  • 鼠标事件:

    • mousemove(拖拽)
    • mouseover(划过)
    • mouseWheel(滚屏)
  • 键盘事件:

    • keydown(按下键盘)
    • keypress(按下字符键盘)
    • keyup(弹起键盘)
  • window resize/scroll

    • DOM 元素动态定位

优化方式是控制事件处理器在一段时间内的执行次数

防抖

频繁(连续)触发事件(比如用户触发输入事件input),不执行目标动作,当不在触发事件了,再执行。

实现思路,在事件处理器内,使用 setTimeout 包裹目标动作,一直触发事件,就清除上次的定时器,不再触发触发事件,会执行最后一个定时器,目标动作也执行一次了。

<input type="text" id="input" />

JS代码:

function debounce(callback, delay) {
  let timeout = 0;
  return e => {
    console.log('清除', timeout, new Date());
    clearTimeout(timeout); //input 一直触发,就清除上一次的定时器,防止执行目标函数,直到事件不触发事件,最后一个定时器没有清除,delay 时间后就会执定时器,就确保了目标函数只执行一次。
    timeout = setTimeout(() => {
      callback(e);
    }, delay);
    console.log('新的', timeout, e.target.value, new Date());
  };
}
let print = debounce(e => {
  let value = e.target.value;
  console.log(value, new Date());
}, 1000);
document
  .querySelector('#input')
  .addEventListener('input', print, false);

清除定时器的时机很关键,在新定时器生成之前,如果在之后,会将所有定时器都清除,目标函数一次都不执行。

节流

防抖是多次触发事件,目标函数只执行一次,不管触发这些事件用了多少时间。而节流是在一段时间内,确保目标函数只执行一次,实现缓慢执行目标函数的效果。

上面的输入使用节流实现:

let thorttle = (callback, delay) => {
  let timeout = 0;
  let now = new Date() - 0;
  return e => {
    console.log('now', now);
    let last = new Date() - 0;
    clearTimeout(timeout);
    if (last - now >= delay) {
      console.log('时间间隔', last - now);
      callback(e);
      now = last;//将上执行的时间赋值给 now
    } else {
      //将 delay 时间内多次触发事件,目标函数合并到这里执行
      timeout = setTimeout(() => {
        callback(e);
      }, delay);
    }
  };
};
let write = thorttle(e => {
  console.log(e.target.value, new Date());
}, 5000);
document
  .querySelector('#input')
  .addEventListener('input', write, false);

两者比较

节流在某个时间段内,目标函数能执行一次,限制目标函数的执行频率,不管事件触发了多少次;
防抖是多次触发事件,目标函数只执行一次,不管触发了这些事件用了多少时间。

节流函数限制目标函数的执行频率,有连续变化的效果,适用于关注变化过程的操作,可以调整目标函数执行频率使得变化更加平滑,比如动画、改变窗口时执行某些操作等,常用事件resizescrollmouseWheeltouchmovemouseover等;

防抖函数适用于更关注结果的操作,不太关注操作过程,常见的事件有 inputkeyup等。

最后看一个 将 防抖 和 节流都用 resize 事件的效果,更能体会两者的区别:

function debounce(callback, delay) {
  let timeout = 0;
  return e => {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      callback(e);
    }, delay);
  };
}
let print = debounce(e => {
  let value = e.target.value;
  console.log('debounce', window.innerWidth);
}, 500);
let thorttle = (callback, delay) => {
  let timeout = 0;
  let now = new Date() - 0;
  return e => {
    let last = new Date() - 0;
    clearTimeout(timeout);
    if (last - now >= delay) {
      callback(e);
      now = last;
    } else {
      timeout = setTimeout(() => {
        callback(e);
      }, delay);
    }
  };
};
let write = thorttle(e => {
  console.log('thorttle', window.innerWidth);
}, 500);
window.addEventListener('resize', write, false);
window.addEventListener('resize', print, false);

参考

函数节流与函数防抖
函数防抖与函数节流


JackZhouMine
184 声望4 粉丝

前端工程师